package com.gwtgeo.ui.client.mvp.map;

import java.util.HashMap;
import java.util.List;

import net.customware.gwt.dispatch.client.DispatchAsync;
import net.customware.gwt.presenter.client.EventBus;

import org.gwtopenmaps.openlayers.client.LonLat;
import org.gwtopenmaps.openlayers.client.Map;
import org.gwtopenmaps.openlayers.client.MapOptions;
import org.gwtopenmaps.openlayers.client.Style;
import org.gwtopenmaps.openlayers.client.control.MousePosition;
import org.gwtopenmaps.openlayers.client.control.NavToolBar;
import org.gwtopenmaps.openlayers.client.control.PanZoomBar;
import org.gwtopenmaps.openlayers.client.control.SelectFeature;
import org.gwtopenmaps.openlayers.client.event.VectorFeatureSelectedListener;
import org.gwtopenmaps.openlayers.client.feature.VectorFeature;
import org.gwtopenmaps.openlayers.client.geometry.Point;
import org.gwtopenmaps.openlayers.client.layer.Layer;
import org.gwtopenmaps.openlayers.client.layer.Vector;
import org.gwtopenmaps.openlayers.client.layer.WMS;
import org.gwtopenmaps.openlayers.client.layer.WMSParams;
import org.gwtopenmaps.openlayers.client.util.JObjectArray;
import org.gwtopenmaps.openlayers.client.util.JSObject;

import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
import com.gwtgeo.service.model.Location;
import com.gwtgeo.service.model.MapDef;
import com.gwtgeo.service.model.WMSLayerDef;
import com.gwtgeo.ui.client.defbuilder.MapDefBuilder;
import com.gwtgeo.ui.client.mvp.Saveable;
import com.gwtgeo.ui.client.mvp.layers.LayersPresenter;
import com.gwtgeo.ui.client.mvp.searchresults.SearchResultsPresenter;
import com.gwtgeo.ui.client.mvp.windowsmanager.WindowTypeEnum;
import com.gwtgeo.ui.client.mvp.windowsmanager.WindowedDisplay;
import com.gwtgeo.ui.client.mvp.windowsmanager.WindowedPresenter;
import com.gwtgeo.ui.client.mvp.windowsmanager.WindowsParams;
import com.gwtgeo.ui.shared.event.AddLayerEvent;
import com.gwtgeo.ui.shared.event.AddLayerEventHandler;
import com.gwtgeo.ui.shared.event.RemoveLayerEvent;
import com.gwtgeo.ui.shared.event.RemoveLayerEventHandler;
import com.gwtgeo.ui.shared.event.RepositionLayerEvent;
import com.gwtgeo.ui.shared.event.RepositionLayerEventHandler;
import com.gwtgeo.ui.shared.event.SearchFinishedEvent;
import com.gwtgeo.ui.shared.event.SearchFinishedEventHandler;
import com.gwtgeo.ui.shared.event.SelectionRetrievedEvent;
import com.gwtgeo.ui.shared.rpc.RetrieveSelection;
import com.gwtgeo.ui.shared.rpc.RetrieveSelectionResult;

public class MapPresenter extends WindowedPresenter<MapPresenter.Display>
		implements Saveable<MapDef, Map> {

	public static final String BASE_LAYER_URL = "http://labs.metacarta.com/wms/vmap0";

	private MapOptions defaultMapOptions;
	private String name;
	private Map map;
	private WMS baseLayer;
	private Vector vectorLayer;
	private SelectFeature selectFeature;
	private final DispatchAsync dispatcher;
	private MapDefBuilder defBuilder;
	private LayersPresenter layersPresenter;
	private SearchResultsPresenter resultsPresenter;

	public interface Display extends WindowedDisplay {
		Map createMapWidget(MapOptions options);
	}

	@Inject
	public MapPresenter(final Display display, final EventBus eventBus,
			final DispatchAsync dispatcher, LayersPresenter layersPresenter,
			SearchResultsPresenter resultsPresenter) {
		super(display, eventBus);
		this.dispatcher = dispatcher;
		this.defBuilder = new MapDefBuilder();
		this.layersPresenter = layersPresenter;
		this.resultsPresenter = resultsPresenter;

		java.util.Map<String, Object> defaultParams = new HashMap<String, Object>();
		defaultParams.put(WindowsParams.MAP_PROJECTION, "EPSG:4326");
		
		WMSLayerDef defaultBaseLayer = new WMSLayerDef();
		defaultBaseLayer.setFormat("image/png");
		defaultBaseLayer.setLayers("basic");
		defaultBaseLayer.setName("Basic WMS");
		defaultBaseLayer.setUrl(MapPresenter.BASE_LAYER_URL);
		defaultParams.put(WindowsParams.MAP_BASE_LAYER, defaultBaseLayer);
		
		this.init(defaultParams);
	}

	@Override
	public void init(java.util.Map<String, Object> params) {

		this.name = (String) params.get(WindowsParams.MAP_NAME);
		this.layersPresenter.init(null);
		this.resultsPresenter.init(null);
		this.defaultMapOptions = new MapOptions();
		this.defaultMapOptions.setControls(new JObjectArray(new JSObject[] {}));
		this.defaultMapOptions.setNumZoomLevels(16);
		this.defaultMapOptions.setProjection((String) params
				.get(WindowsParams.MAP_PROJECTION));
		this.map = this.display.createMapWidget(this.defaultMapOptions);

		this.addBaseLayer((WMSLayerDef) params.get(WindowsParams.MAP_BASE_LAYER));
		
		this.addVectorLayer();
		this.addControls();
		Float longitude = (Float) params
				.get(WindowsParams.MAP_CENTER_LONGITUDE);
		if (longitude == null) {
			longitude = new Float(-3.699875);
		}
		Float latitude = (Float) params
				.get(WindowsParams.MAP_CENTER_LATITUDE);
		if (latitude == null) {
			latitude = new Float(40.434718);
		}
		LonLat center = new LonLat(longitude.doubleValue(), latitude
				.doubleValue());
		this.map.setCenter(center, 10);
	}

	@Override
	public WindowTypeEnum getType() {
		return WindowTypeEnum.MAP;
	}

	private void addBaseLayer(WMSLayerDef def) {
		WMSParams wmsParams = new WMSParams();
		wmsParams.setFormat(def.getFormat());
		wmsParams.setLayers(def.getLayers());

		this.baseLayer = new WMS(def.getName(), def.getUrl(),
				wmsParams);

		this.map.addLayer(this.baseLayer);
	}

	private void addVectorLayer() {

		this.vectorLayer = new Vector("Vector Layer");
		this.map.addLayer(this.vectorLayer);
		this.selectFeature = new SelectFeature(this.vectorLayer);
		this.map.addControl(this.selectFeature);
		this.selectFeature.activate();
	}

	private void addControls() {
		this.map.addControl(new PanZoomBar());
		this.map.addControl(new NavToolBar());
		this.map.addControl(new MousePosition());
	}

	private void drawLocations(List<Location> locations) {

		this.vectorLayer.destroyFeatures();

		for (Location location : locations) {

			Point point = new Point(location.getLatitude(), location
					.getLongitude());
			Style pointStyle = new Style();
			pointStyle.setExternalGraphic("images/markers/green.png");
			pointStyle.setGraphicSize(30, 30);
			VectorFeature pointFeature = new VectorFeature(point, pointStyle);
			vectorLayer.addFeature(pointFeature);
			vectorLayer
					.addVectorFeatureSelectedListener(new VectorFeatureSelectedListener() {

						@Override
						public void onFeatureSelected(
								FeatureSelectedEvent eventObject) {
							VectorFeature feature = eventObject
									.getVectorFeature();
							dispatcher
									.execute(
											new RetrieveSelection(feature
													.getCenterLonLat().lon(),
													feature.getCenterLonLat()
															.lat()),
											new AsyncCallback<RetrieveSelectionResult>() {

												@Override
												public void onFailure(
														Throwable caught) {
													// TODO PMA20100207 Handle
													// failure.
												}

												@Override
												public void onSuccess(
														RetrieveSelectionResult result) {
													eventBus
															.fireEvent(new SelectionRetrievedEvent(
																	result
																			.getLocations()));
												}

											});

						}

					});
		}
	}

	@Override
	protected void onBind() {
		super.onBind();
		this.registerHandler(this.eventBus.addHandler(SearchFinishedEvent.TYPE,
				new SearchFinishedEventHandler() {

					@Override
					public void onSearchFinished(SearchFinishedEvent event) {
						MapPresenter.this.drawLocations(event.getLocations());
					}
				}));
		this.registerHandler(this.eventBus.addHandler(AddLayerEvent.TYPE,
				new AddLayerEventHandler() {

					@Override
					public void onAddLayer(AddLayerEvent event) {
						addLayer(event.getLayer());
						System.out.println('\n');
						for (Layer layer : map.getLayers()) {
							System.out.println(layer.getName());
						}
					}
				}));
		this.registerHandler(this.eventBus.addHandler(RemoveLayerEvent.TYPE,
				new RemoveLayerEventHandler() {

					@Override
					public void onRemoveLayer(RemoveLayerEvent event) {
						map.removeLayer(event.getLayer());
					}
				}));
		this.registerHandler(this.eventBus.addHandler(
				RepositionLayerEvent.TYPE, new RepositionLayerEventHandler() {

					@Override
					public void onRepositionLayer(RepositionLayerEvent event) {
						repositionLayer(event.getLayer(), event.getIndex());
					}
				}));
	}

	private void addLayer(Layer layer) {
		map.addLayer(layer);
		map.setLayerIndex(this.vectorLayer, map.getNumLayers() - 1);
	}

	private void repositionLayer(Layer layer, int index) {
		map.setLayerIndex(layer, index + 1);
	}

	@Override
	public Map load(MapDef def) {
		List<WMS> layers = (List<WMS>) layersPresenter.load(def.getLayerDefs());
		java.util.Map<String, Object> params = new HashMap<String, Object>();
		params.put(MapDefBuilder.MAP, this.map);
		params.put(MapDefBuilder.LAYERS, layers);
		this.map = defBuilder.buildObj(def, params);
		this.addVectorLayer();
		List<Location> locations = (List<Location>) resultsPresenter.load(def
				.getLocations());
		this.drawLocations(locations);

		return null;
	}

	@Override
	public MapDef buildSaveableModel() {

		java.util.Map<String, Object> params = new HashMap<String, Object>();
		params.put(MapDefBuilder.WIDTH, "800px");
		params.put(MapDefBuilder.HEIGHT, "600px");
		params.put(MapDefBuilder.ZOOM, this.map.getZoom());
		params.put(MapDefBuilder.NUM_ZOOM_LEVELS, 16);
		MapDef mapDef = defBuilder.buildDef(this.map, params);
		List<WMSLayerDef> layeraDefs = layersPresenter.buildSaveableModel();
		mapDef.setLayerDefs(layeraDefs);
		List<Location> locations = resultsPresenter.buildSaveableModel();
		mapDef.setLocations(locations);

		return mapDef;
	}

	@Override
	protected void onRevealDisplay() {
		// TODO PMA20100207 Think.
	}

	@Override
	protected void onUnbind() {
		// TODO PMA20100207 Think.
	}

	@Override
	public void retrieveWinData(WindowTypeEnum source,
			java.util.Map<String, Object> data) {
		if (source.equals(WindowTypeEnum.NEW)) {
			MapPresenter.this.init(data);
		}
	}

	public String getName() {
		return name;
	}
	
	
}
